package mygame;

import character.ai.AIControl;
import character.CharacterSettings;
import character.AbstractCharacter;
import character.CharacterStatus;
import com.jme3.animation.AnimChannel;
import com.jme3.animation.AnimControl;
import com.jme3.animation.AnimEventListener;
import com.jme3.animation.LoopMode;
import com.jme3.bullet.collision.PhysicsCollisionObject;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.control.CharacterControl;
import com.jme3.bullet.control.GhostControl;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import java.io.IOException;

public class MyCharacterControl extends CharacterControl implements AnimEventListener {
    public static final float ANIMATION_BLEND_TIME = 0.3f;
    protected AbstractCharacter thisCharacter; //just a AbstractCharacter reference to the spatial to avoid constant casting

    public MyCharacterControl() {}

    public MyCharacterControl(CollisionShape shape, float stepHeight) {
	super(shape, stepHeight);
    }

    @Override
    public void update(float tpf) {
	checkForCollisions();
        updateWalkDirection();
	super.update(tpf);
    }

    /**
     * Check for collisions so they can be handled and reported to any attached AIControls.
     * This method uses the AbstractCharacter's GhostControl, which is assumed to have been attached
     * to the AbstractCharacter's model at creation time.
     */
    protected void checkForCollisions() {
	for(PhysicsCollisionObject collision : thisCharacter.getModel().getControl(GhostControl.class).getOverlappingObjects()) {
	    Spatial collidingSpatial = (Spatial)collision.getUserObject();
	    if(!collidingSpatial.equals(thisCharacter)) {
                //TODO find a better way to ignore collisions with what we're standing on
                
		if(collidingSpatial instanceof AbstractCharacter)
		    collideCharacters((AbstractCharacter)collidingSpatial);
                //alert attached AI control of collision
                if(thisCharacter.getControl(AIControl.class) != null)
                    thisCharacter.getControl(AIControl.class).collideWith(collidingSpatial);
	    }
	}
    }

    public void collideCharacters(AbstractCharacter collidingCharacter) {
	//separate spatials if they're both characters
	CharacterControl charControl = collidingCharacter.getControl(MyCharacterControl.class);
	//TODO separate colliding characters using mathemagic
        
	charControl.jump();
    }
    
    protected void updateWalkDirection() {
        //this control doesn't care where the char is going, it just wants to know the walk direction at this moment
	CharacterStatus charStatus = thisCharacter.getStatus();
	if(charStatus.getMoveDir() != null) {
            //let's not modify the stored value, but rather a copy
            Vector3f moveDirCloned = charStatus.getMoveDir().clone();
	    moveDirCloned.normalizeLocal();
	    moveDirCloned.multLocal(charStatus.getMoveSpeed()/100f);
            if(!walkDirection.equals(moveDirCloned)) { //if the walkDirection status changed
                setWalkDirection(moveDirCloned);
                updateWalkAnimation();
            }

	    if(!moveDirCloned.equals(Vector3f.ZERO)) { //exclude a zero vector so we don't face the origin when stopped
		setViewDirection(moveDirCloned);
	    }
	} else {
	    charStatus.setMoveDir(Vector3f.ZERO); //course-correct a null moveDir
	    updateWalkDirection();
	}
    }

    public void updateWalkAnimation() {
        //TODO there's something funky going on here, maybe only after loading a game. keep an eye out
	AnimControl animControl = thisCharacter.getModel().getControl(AnimControl.class);
	if(animControl != null) {
	    AnimChannel walkChannel = animControl.getChannel(AbstractCharacter.CHARACTER_ANIM_CHANNEL_WALK);
	    if(!walkDirection.equals(Vector3f.ZERO)) {
		if(!walkChannel.getAnimationName().equals(AbstractCharacter.CHARACTER_ANIM_WALK)) {
		    walkChannel.setAnim(AbstractCharacter.CHARACTER_ANIM_WALK, ANIMATION_BLEND_TIME);
		}
	    } else {
		walkChannel.setAnim(AbstractCharacter.CHARACTER_ANIM_STAND, ANIMATION_BLEND_TIME);
	    }
	}
    }

    public void run(boolean isRunning) {
	if(isRunning) {
	    thisCharacter.getStatus().setMoveSpeed(thisCharacter.getSettings().getRunSpeed());
	} else {
	    thisCharacter.getStatus().setMoveSpeed(thisCharacter.getSettings().getWalkSpeed());
	}
    }

    public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
	if(channel.getLoopMode() == LoopMode.DontLoop) {
	    channel.setAnim(AbstractCharacter.CHARACTER_ANIM_STAND, ANIMATION_BLEND_TIME);
	}
    }

    public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {}
    
    public Vector3f getFeetWorldTranslation() {
	Vector3f feet = getPhysicsLocation().clone();
	CharacterSettings charSettings = thisCharacter.getSettings();
	feet.setY(feet.getY() - charSettings.getHeight()/2);
	return feet;
    }

    @Override
    public void setSpatial(Spatial spatial) {
	super.setSpatial(spatial);
	thisCharacter = (AbstractCharacter)spatial;
    }

    @Override
    public void write(JmeExporter ex) throws IOException {
	super.write(ex);
	OutputCapsule capsule = ex.getCapsule(this);
    }

    @Override
    public void read(JmeImporter im) throws IOException {
	super.read(im);
	InputCapsule capsule = im.getCapsule(this);
	thisCharacter = (AbstractCharacter)spatial;
    }
}
